/*******************************************************************************
#                                                                              #
#      MJPG-streamer allows to stream JPG frames from an input-plugin          #
#      to several output plugins                                               #
#                                                                              #
#      Copyright (C) 2007 Tom Stöveken                                         #
#                                                                              #
# This program is free software; you can redistribute it and/or modify         #
# it under the terms of the GNU General Public License as published by         #
# the Free Software Foundation; version 2 of the License.                      #
#                                                                              #
# This program is distributed in the hope that it will be useful,              #
# but WITHOUT ANY WARRANTY; without even the implied warranty of               #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                #
# GNU General Public License for more details.                                 #
#                                                                              #
# You should have received a copy of the GNU General Public License            #
# along with this program; if not, write to the Free Software                  #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA    #
#                                                                              #
*******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <pthread.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>
#include <dirent.h>

#include "../../utils.h"
#include "../../mjpg_streamer.h"

#define OUTPUT_PLUGIN_NAME "UDP output plugin"

static pthread_t worker;
static globals *pglobal;
static int fd, delay, max_frame_size;
static char *folder = "/tmp";
static unsigned char *frame = NULL;
static char *command = NULL;
static int input_number = 0;
static int port = 0;

// 流传输状态
static int streaming_active = 0;
static struct sockaddr_in client_addr;
static socklen_t client_addr_len = 0;

void help(void)
{
    fprintf(stderr, " ---------------------------------------------------------------\n"
                    " Help for output plugin..: " OUTPUT_PLUGIN_NAME "\n"
                    " ---------------------------------------------------------------\n"
                    " The following parameters can be passed to this plugin:\n\n"
                    " [-f | --folder ]........: folder to save pictures\n"
                    " [-d | --delay ].........: delay after saving pictures in ms\n"
                    " [-c | --command ].......: execute command after saveing picture\n"
                    " [-p | --port ]..........: UDP port to listen for requests\n\n"
                    " [-i | --input ].........: input plugin number (default: 0)\n\n"
                    " ---------------------------------------------------------------\n");
}

void worker_cleanup(void *arg)
{
    static unsigned char first_run = 1;

    if (!first_run)
    {
        DBG("already cleaned up resources\n");
        return;
    }

    first_run = 0;
    OPRINT("cleaning up resources allocated by worker thread\n");

    if (frame != NULL)
    {
        free(frame);
        frame = NULL;
    }
}

void save_photo(const char *filename, int frame_size)
{
    if (strlen(filename) == 0)
    {
        // 生成时间戳文件名
        time_t now = time(NULL);
        struct tm *t = localtime(&now);
        char timestamp[20];
        snprintf(timestamp, sizeof(timestamp), "%04d%02d%02d_%02d%02d%02d.jpg",
                 t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
                 t->tm_hour, t->tm_min, t->tm_sec);

        char fullpath[256];
        snprintf(fullpath, sizeof(fullpath), "%s/%s", folder, timestamp);

        fd = open(fullpath, O_CREAT | O_RDWR | O_TRUNC, 0644);
        if (fd < 0)
        {
            perror("open photo file");
            return;
        }
    }
    else
    {
        fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
        if (fd < 0)
        {
            perror("open photo file");
            return;
        }
    }

    if (write(fd, frame, frame_size) < 0)
    {
        perror("write photo");
    }
    close(fd);
}

void *worker_thread(void *arg)
{
    int ok = 1, frame_size = 0;
    int stream_socket = socket(PF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in server_addr;
    int flags;

    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    if (bind(stream_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
    {
        perror("bind UDP socket");
        close(stream_socket);
        return NULL;
    }

    // 设置套接字为非阻塞
    flags = fcntl(stream_socket, F_GETFL);
    fcntl(stream_socket, F_SETFL, flags | O_NONBLOCK);

    OPRINT("UDP server started on port %d\n", port);

    pthread_cleanup_push(worker_cleanup, NULL);

    while (ok && !pglobal->stop)
    {
        fd_set read_fds;
        struct timeval tv;
        int ret;
        char udpbuffer[1024];
        struct sockaddr_in client;
        socklen_t client_len = sizeof(client);

        // 准备文件描述符集
        FD_ZERO(&read_fds);
        FD_SET(stream_socket, &read_fds);
        tv.tv_sec = 0;
        tv.tv_usec = 50000; // 50ms超时

        ret = select(stream_socket + 1, &read_fds, NULL, NULL, &tv);

        // 处理UDP指令
        if (ret > 0 && FD_ISSET(stream_socket, &read_fds))
        {
            ssize_t bytes = recvfrom(stream_socket, udpbuffer, sizeof(udpbuffer) - 1, 0,
                                     (struct sockaddr *)&client, &client_len);

            if (bytes > 0)
            {
                udpbuffer[bytes] = '\0'; // 确保字符串终止

                // 处理指令
                if (strcmp(udpbuffer, "get_stream") == 0)
                {
                    streaming_active = 1;
                    memcpy(&client_addr, &client, sizeof(client_addr));
                    client_addr_len = client_len;
                    OPRINT("Start streaming to %s:%d\n",
                           inet_ntoa(client_addr.sin_addr),
                           ntohs(client_addr.sin_port));

                    // 发送确认
                    sendto(stream_socket, "stream_started", 14, 0,
                           (struct sockaddr *)&client_addr, client_addr_len);
                }
                else if (strcmp(udpbuffer, "stop_stream") == 0)
                {
                    streaming_active = 0;
                    OPRINT("Stop streaming\n");
                }
                else if (strncmp(udpbuffer, "get_photo", 9) == 0)
                {
                    // 提取文件名（如果有）
                    const char *filename = "";
                    if (bytes > 10)
                        filename = udpbuffer + 10;

                    // 处理照片请求
                    if (frame != NULL)
                    {
                        save_photo(filename, max_frame_size);
                        // 发送确认
                        sendto(stream_socket, "photo_saved", 11, 0,
                               (struct sockaddr *)&client, client_len);
                    }
                }
            }
        }

        // 获取最新视频帧
        pthread_mutex_lock(&pglobal->in[input_number].db);
        frame_size = pglobal->in[input_number].size;

        if (frame_size > max_frame_size)
        {
            unsigned char *tmp = realloc(frame, frame_size + 1024);
            if (!tmp)
            {
                pthread_mutex_unlock(&pglobal->in[input_number].db);
                LOG("Memory allocation failed\n");
                break;
            }
            frame = tmp;
            max_frame_size = frame_size + 1024;
        }

        memcpy(frame, pglobal->in[input_number].buf, frame_size);
        pthread_mutex_unlock(&pglobal->in[input_number].db);
//**
        // 在发送帧前添加(发送日志)
        // printf("Sending frame of size %d to %s:%d\n",
        //        frame_size,
        //        inet_ntoa(client_addr.sin_addr),
        //        ntohs(client_addr.sin_port));
//**
        // 流式传输视频帧
        if (streaming_active && frame_size > 0) 
        {
            ssize_t sent = sendto(stream_socket, frame, frame_size, 0,
                                  (struct sockaddr *)&client_addr, client_addr_len);
            //*调试输出
            //printf("%d\n", frame_size);
            if (sent == -1)
            {
                perror("sendto failed");
            }
            else
            {
                // 调试输出
                //printf("Sent frame to client: %ld bytes\n", sent);
            }
            //*
        }

        // 处理延时
        if (delay > 0)
        {
            usleep(1000 * delay);
        }
    }

    // 清理
    close(stream_socket);
    pthread_cleanup_pop(1);
    return NULL;
}

int output_init(output_parameter *param)
{
    int i;

    delay = 0;

    param->argv[0] = OUTPUT_PLUGIN_NAME;

    for (i = 0; i < param->argc; i++)
    {
        DBG("argv[%d]=%s\n", i, param->argv[i]);
    }

    reset_getopt();
    while (1)
    {
        int option_index = 0, c = 0;
        static struct option long_options[] = {
            {"h", no_argument, 0, 0},
            {"help", no_argument, 0, 0},
            {"f", required_argument, 0, 0},
            {"folder", required_argument, 0, 0},
            {"d", required_argument, 0, 0},
            {"delay", required_argument, 0, 0},
            {"c", required_argument, 0, 0},
            {"command", required_argument, 0, 0},
            {"p", required_argument, 0, 0},
            {"port", required_argument, 0, 0},
            {"i", required_argument, 0, 0},
            {"input", required_argument, 0, 0},
            {0, 0, 0, 0}};

        c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index);

        if (c == -1)
            break;
        if (c == '?')
        {
            help();
            return 1;
        }

        switch (option_index)
        {
        case 0:
        case 1: // help
            help();
            return 1;
        case 2:
        case 3: // folder
            folder = strdup(optarg);
            if (folder[strlen(folder) - 1] == '/')
                folder[strlen(folder) - 1] = '\0';
            break;
        case 4:
        case 5: // delay
            delay = atoi(optarg);
            break;
        case 6:
        case 7: // command
            command = strdup(optarg);
            break;
        case 8:
        case 9: // port
            port = atoi(optarg);
            break;
        case 10:
        case 11: // input
            input_number = atoi(optarg);
            break;
        }
    }

    pglobal = param->global;
    if (input_number >= pglobal->incnt)
    {
        OPRINT("Input plugin number %d invalid (max %d)\n", input_number, pglobal->incnt - 1);
        return 1;
    }

    // 初始化帧缓冲区
    max_frame_size = 1024 * 1024; // 1MB初始大小
    frame = malloc(max_frame_size);
    if (!frame)
    {
        OPRINT("Memory allocation failed\n");
        return 1;
    }

    OPRINT("UDP output plugin configured:\n");
    OPRINT(" Input plugin.....: %d\n", input_number);
    OPRINT(" Output folder....: %s\n", folder);
    OPRINT(" Delay.............: %d ms\n", delay);
    OPRINT(" Command...........: %s\n", command ? command : "None");
    OPRINT(" UDP port..........: %d\n", port);

    return 0;
}

int output_stop(int id)
{
    pthread_cancel(worker);
    return 0;
}

int output_run(int id)
{
    pthread_create(&worker, 0, worker_thread, NULL);
    pthread_detach(worker);
    return 0;
}